home *** CD-ROM | disk | FTP | other *** search
- /* FindNthPalindrome by Stepan Riha
- *
- * This algorithm works with the following
- * fact in mind: An N-digit string can form
- * 9 * 10^((N-1)/2) palindromes:
- *
- * Odd N: Even N:
- * 1st 100..000..001 100..0000..001
- * 2nd 100..010..001 100..0110..001
- * 3rd 100..020..001 100..0220..001
- * 100.......001 100........001
- * 10th 100..090..001 100..0990..001
- * 11th 100..101..001 100..1001..001
- * 12th 100..111..001 100..1111..001
- * ............. ..............
- * last-1 999..989..999 999..9889..999
- * last 999..999..999 999..9999..999
- *
- * Speedups are achieved bye replacing x*10
- * with (x<<1 + x<<3) and by doing conversions
- * requireing x/10 and x%10 on my own
- * approximating 1/10 with 3/32. Also, I use
- * a lookup table for powers of 10, etc.
- */
-
- /* Precomputed values:
- palins10 is an array of 9*10^((n-1)/2)
- multpow10 is a multiplication table of
- 10^n (last row is all 10^9
- because of integer overflow)
- */
-
- static long palins10[] = {
- 9L, 9L, 90L, 90L, 900L, 900L, 9000L,
- 9000L, 90000L, 90000L, 900000L
- };
-
- static long multpow10[100] = {
- 0L, 1L, 2L, 3L, 4L,
- 5L, 6L, 7L, 8L, 9L,
- 0L, 10L, 20L, 30L, 40L,
- 50L, 60L, 70L, 80L, 90L,
- 0L, 100L, 200L, 300L, 400L,
- 500L, 600L, 700L, 800L, 900L,
- 0L, 1000L, 2000L, 3000L, 4000L,
- 5000L, 6000L, 7000L, 8000L, 9000L,
- 0L, 10000L, 20000L, 30000L, 40000L,
- 50000L, 60000L, 70000L, 80000L, 90000L,
- 0L, 100000L, 200000L, 300000L, 400000L,
- 500000L, 600000L, 700000L, 800000L,
- 900000L,
- 0L, 1000000L, 2000000L, 3000000L, 4000000L,
- 5000000L, 6000000L, 7000000L, 8000000L,
- 9000000L,
- 0L, 10000000L, 20000000L, 30000000L,
- 40000000L, 50000000L, 60000000L, 70000000L,
- 80000000L, 90000000L,
- 0L, 100000000L, 200000000L, 300000000L,
- 400000000L, 500000000L, 600000000L,
- 700000000L, 800000000L, 900000000L,
- 0L, 1000000000L, 1000000000L, 1000000000L,
- 1000000000L, 1000000000L, 1000000000L,
- 1000000000L, 1000000000L, 1000000000L
- };
-
- /* (x * 10) */
- #define Times10(x) ((x<<1) + (x<<3))
- /* x *= 10 */
- #define MultBy10(x) x += x + (x<<3)
- /* (x * 3 / 32) */
- #define ApproxDiv10(x) ((x>>4)+(x>>5))
- /* (x/2) */
- #define Half(x) ((x)>>1)
-
- long FindNthPalindrome(long baseNumber, short n);
-
- long FindNthPalindrome(register long base, short n)
- {
- register long num, dig, mult10, remain,
- *lop, *hip, *multpow;
- long buffer[16];
-
- if (base >= 1000000000L)
- /* to big to start with */
- return -1L;
-
- if (!(num = n))
- /* 0th palindrome doesn't exist */
- return base;
-
- /* palindrome =
- * n-th palindrome > baseNumber */
-
- /* Find dig = (number of digits in
- * baseNumber) - 1
- * Find how many dig-digit palindromes
- * are smaller than the baseNumber
- */
-
- if (base < 10) { /* baseNumber <= 9 */
- dig = 0;
- /* all 1-digit strings are
- * palindromes (duh!)
- */
- num += base - 1;
- }
- else { /* baseNumber is >= 10 */
- /* Convert baseNumber to string.
- * This is faster than using /1
- * and %10
- */
- lop = buffer; dig = -1;
- while (base) {
- remain = base; base = 0;
- goto division1;
- while (mult10) {
- base += mult10;
- remain -= Times10(mult10);
- division1:
- mult10 = ApproxDiv10(remain);
- }
- if (remain >= 10) {
- base++; remain -= 10;
- }
- *lop++ = remain; dig++;
- }
- /* Find how many dig-digit palindromes
- * are smaller than this string
- * Start searching from the middle
- */
- hip = (lop = buffer) + Half(dig+1);
- lop += (base = Half(dig));
- multpow = multpow10;
- for(; base >= 0 && *lop==*hip;
- hip++, lop--, multpow += 10, base--)
- num += multpow[*hip];
- if (base >= 0) {
- if (*hip > *lop)
- num--;
- for(; base >= 0;
- hip++, multpow += 10, base--)
- num += multpow[*hip];
- }
- /* adjust for no leading zero */
- num -= multpow[-9];
- } /* if (baseNumber < 10) */
-
- /* palindrome =
- * num-th palindrome > 10^dig */
-
- /* Find how many digits the palindrome
- * will have
- */
- hip = (lop = palins10) + dig;
- while (num >= 0)
- num -= *hip++;
- num += *(--hip);
- dig = hip - lop;
- if (dig >= 9)
- /* i.e. baseNumber > 999999999
- * (dig is always <= 10) */
- return -1L;
-
- /* palindrome =
- * num-th dig-digit palindrome */
-
- /* Find the num-th dig-digit paindrome.
- * Calculate final palindrome string. This
- * is faster than using /10 and %10
- * Use lop and hip to point in the correct
- * row in the multiplication table
- * Start filling digits from the middle of
- * the string
- */
- base = Half(dig+1);
- hip = (lop = multpow10) + Times10(base);
- base = Half(dig);
- lop += Times10(base);
- base = 0L;
- while (num) {
- remain = num; num = 0;
- goto division2;
- while (mult10) {
- num += mult10;
- remain -= Times10(mult10);
- division2:
- mult10 = ApproxDiv10(remain);
- }
- if (remain >= 10) {
- num++; remain -= 10;
- }
- base += lop[remain];
- if (lop < hip)
- /* don't count middle digit twice */
- base += hip[remain];
- lop -= 10; hip += 10;
- }
- /* adjust for no leading zero */
- lop = multpow10 + 1;
- base += *lop;
- if (dig) {
- MultBy10(dig);
- base += lop[dig];
- }
-
- return base;
- }
-
-